#include "gui.hpp"
#include "log.hpp"
#include "sdltool.hpp"
#include "encoding.hpp"
#include <SDL_events.h>

namespace sdl_engine::gui::event {
    enum MouseButton {
        UNKNOWN = -1,
        LEFT,
        MIDDLE,
        RIGHT,
    };

    struct {
        std::array<bool, 3> buttonDown;
        SDL_Event event;
        bool isEditing = false;
    } EventContext;

    MouseButton CvtSDLMouseButton(u8 button) {
        if (button == SDL_BUTTON_LEFT)
            return LEFT;
        else if (button == SDL_BUTTON_MIDDLE)
            return MIDDLE;
        else if (button == SDL_BUTTON_RIGHT)
            return RIGHT;
        return UNKNOWN;
    }

    Point MousePosition() {
        return engine.GetMousePosition();
    }
}

namespace sdl_engine::gui {

struct {
    Renderer* renderer = nullptr;
    Font* font = nullptr;
    Stock<Widget*> stock;
    Widget* focuse = nullptr;
    int wheel = 0;
} GuiContext;

void Init(Renderer& renderer, Font* defaultFont) {
    GuiContext.renderer = &renderer;
    GuiContext.font = defaultFont;
}

void Shutdown() {
    FreeAllWidgets();
}

void recordEventData(const SDL_Event& e) {
    auto& context = event::EventContext;
    if (e.type == SDL_MOUSEBUTTONDOWN) {
        auto btn = event::CvtSDLMouseButton(e.button.button);
        if (btn != event::UNKNOWN) {
            context.buttonDown[btn] = true;
        }
    }
    if (e.type == SDL_MOUSEBUTTONUP) {
        auto btn = event::CvtSDLMouseButton(e.button.button);
        if (btn != event::UNKNOWN) {
            context.buttonDown[btn] = false;
        }
    }
    if (e.type == SDL_TEXTEDITING) {
        if (strlen(e.edit.text) == 0) {
            context.isEditing = false;
        } else {
            context.isEditing = true;
        }
    }
    if (e.type == SDL_TEXTINPUT) {
        context.isEditing = false;
    }
    if (e.type == SDL_MOUSEWHEEL) {
        GuiContext.wheel = e.wheel.y;
        if (e.wheel.direction == SDL_MOUSEWHEEL_FLIPPED) {
            GuiContext.wheel = -e.wheel.y;
        }
    }
}

Event judgeEventType(const SDL_Event& e) {
    Event event = UNKNOWN;
    if (e.type == SDL_MOUSEBUTTONDOWN) {
        event = MOUSEDOWN;
    }
    if (e.type == SDL_MOUSEBUTTONUP) {
        event = MOUSEUP;
    }
    if (e.type == SDL_MOUSEMOTION) {
        if (event::EventContext.buttonDown[event::LEFT]) {
            event = DRAG;
        } else {
            event = MOUSEMOTION;
        }
    }
    if (e.type == SDL_MOUSEWHEEL) {
        event = MOUSEWHEEL;
    }
    if (e.type == SDL_TEXTINPUT) {
        event = TEXT_INPUT;
    }
    if (e.type == SDL_TEXTEDITING) {
        event = TEXT_EDITING;
    }
    if (e.type == SDL_KEYDOWN) {
        event = KEYDOWN;
    }
    if (e.type == SDL_KEYUP) {
        event = KEYUP;
    }

    return event;
}

void EventHandle(const SDL_Event& e) {
    recordEventData(e);
    auto event = judgeEventType(e);

    if (event != UNKNOWN) {
        if (event == MOUSEDOWN) {
            SetFocuse(nullptr);
            for (auto& widget: GuiContext.stock.GetContainer()) {
                if (!widget->parent_ &&
                    PointInRect(event::MousePosition(), widget->GetRect())) {
                    SetFocuse(widget);
                }
            }
        }
        if (GetFocuse()) {
            GetFocuse()->EventHandle(event);
        }
    }
}

/***********************
 * Widget
***********************/

Widget::Widget(int id): id_(id) {}

void Widget::SetPosition(const Point& p) {
    rect_.position = p;
}

void Widget::SetSize(const Size& size) {
    rect_.size = size;
}

Rect Widget::GetRect() const {
    return rect_;
}

void Widget::EventHandle(Event type) {
    if (type == GET_FOCUSE) {
        if (eventListener_.onFocuseCb_)
            eventListener_.onFocuseCb_(this);
    }
    if (type == LOST_FOCUSE) {
        if (eventListener_.lostFocuseCb_)
            eventListener_.lostFocuseCb_(this);
    }
}

void Widget::Render() {
    Rect clipRect = GuiContext.renderer->GetClipRect();
    bool isTop = false;
    if (clipRect.position.x == 0 && clipRect.position.y == 0 &&
        clipRect.size.w == 0 && clipRect.size.h == 0) {
        isTop = true;
    }
    Rect rect = GetRect();
    if (isTop) {
        GuiContext.renderer->SetClipRect(rect);
        renderLocal();
    } else if (IsRectsIntersect(rect, clipRect)) {
        Rect finalRect = IntersectRects(rect, clipRect);
        GuiContext.renderer->SetClipRect(finalRect);
        renderLocal();
    }

    if (isTop) {
        GuiContext.renderer->SetClipRect2Default();
    } else {
        GuiContext.renderer->SetClipRect(clipRect);
    }
    renderGlobal();
}

/**********************
 * Containable
**********************/

Containable::Containable(int id): Widget(id) {}

void Containable::SetPosition(const Point& p) {
    Widget::SetPosition(p);
    Pack();
}

void Containable::SetSize(const Size& s) {
    Widget::SetSize(s);
    Pack();
}


/***********************
 * LinearLayout
***********************/

void LinearLayout::AddWidget(Widget* widget) {
    Size size = GetSize();
    Point pos = GetPosition();
    auto& widgets = this->GetWidgets();
    widget->SetParent(this);
    widgets.push_back(widget);
    Pack();
}

void LinearLayout::Pack() {
    Point pos = GetPosition();
    Size s = GetSize();
    for (auto& widget : GetWidgets()) {
        widget->SetPosition(pos);
        if (vertical_) {
            pos.y += widget->GetSize().h;
        } else {
            pos.x += widget->GetSize().w;
        }
        s.w = std::max(s.w,
                       widget->GetPosition().x +
                       widget->GetSize().w -
                       GetPosition().x);
        s.h = std::max(s.h,
                       widget->GetPosition().y +
                       widget->GetSize().h -
                       GetPosition().y);
    }
    SetSizeNoPack(s);
}

void LinearLayout::renderLocal() {
    LayoutRender(*this);
}


/***********************
 * GridLayout
***********************/

std::vector<Widget*> GridLayout::GetWidgets() {
    std::vector<Widget*> result;
    for (auto& widget : widgets_) {
        result.push_back(widget.widget);
    }
    return result;
}

void GridLayout::Pack() {
    for (size_t i = 0; i < widgets_.size(); i++) {
        packOneWidget(widgets_[i].widget,
                      widgets_[i].row, widgets_[i].col,
                      widgets_[i].rowspan,
                      widgets_[i].colspan);
    }
}

void GridLayout::packOneWidget(Widget* widget,
                               int row, int col, int rowspan, int colspan) {
    Point pos = GetPosition() + Point(col * gridSize_.w, row * gridSize_.h);
    widget->SetPosition(pos);
    widget->SetSize(size(colspan * gridSize_.w, rowspan * gridSize_.h));
    Size s = this->GetSize();
    s.w = std::max(s.w,
                   widget->GetPosition().x +
                   widget->GetSize().w -
                   this->GetPosition().x);
    s.h = std::max(s.h,
                   widget->GetPosition().y +
                   widget->GetSize().h -
                   this->GetPosition().y);
    this->SetSizeNoPack(s);
}

void GridLayout::AddWidget(Widget* widget,
                           int row, int col,
                           int rowspan, int colspan) {
    SDL_Rect rect = {col * gridSize_.w, row * gridSize_.h,
                     colspan * gridSize_.w, rowspan * gridSize_.h};
    for (auto& w: widgets_) {
        SDL_Rect r = {w.col * gridSize_.w, w.row * gridSize_.h,
                      w.colspan * gridSize_.w, w.rowspan * gridSize_.h};
        if (SDL_IntersectRect(&rect, &r, nullptr)) {
            return;
        }
    }

    packOneWidget(widget, row, col, rowspan, colspan);
    widget->SetParent(this);
    WidgetInfo info{widget, row, col, rowspan, colspan};
    widgets_.push_back(info);
}

void GridLayout::renderLocal() {
    LayoutRender(*this);
}


/***********************
 * AbsoluteLayout 
***********************/

void AbsoluteLayout::renderLocal() {
    LayoutRender(*this);
}

void AbsoluteLayout::Pack() {}


/***********************
 * Window
***********************/

Window::Window(int id,
               const Point& pos,
               const Size& size,
               const std::string& title,
               bool showTitleBar)
    :Containable(id), showTitleBar_(showTitleBar) {
    SetPosNoPack(pos);
    SetSizeNoPack(size + Size{0, GuiContext.font->GetFontHeight()});
    scrollbar_.reset(new Slidebar(ID_ANY,
                                   0, GetSize().h, 0, GetContentSize().h,
                                   true));
    titleTextTexture_.reset(new TextTexture(*GuiContext.font));
    titleTextTexture_->SetText(title);
    scrollbar_->SetPosition(point(GetContentRect().position.x +
                                  GetContentRect().size.w -
                                  scrollbar_->GetWidth(),
                                  GetContentRect().position.y));
    scrollbar_->SetParent(this);

    Pack();
}

Size Window::GetContentSize() const {
    if (showTitleBar_)
        return size(GetSize().w, GetSize().h - GuiContext.font->GetFontHeight());
    return GetSize();
}

Rect Window::GetContentRect() const {
    if (showTitleBar_) {
        return rect(GetPosition().x,
                    GetPosition().y + GuiContext.font->GetFontHeight(),
                    GetContentSize().w, GetContentSize().h);
    }
    return rect(GetPosition().x, GetPosition().y,
                GetContentSize().w, GetContentSize().h);
}

void Window::SetLayout(Layout* layout) {
    layout_ = layout;
    layout_->SetParent(this);
    layout_->SetPosition(GetPosition() + point(0, GuiContext.font->GetFontHeight()));
}

void Window::SetPosition(const Point& p) {
    SetPosNoPack(p);
    if (layout_)
        layout_->SetPosition(GetPosition() + point(0, GuiContext.font->GetFontHeight()));
}

void Window::SetSize(const Size& s) {
    SetSizeNoPack(s);
    if (layout_)
        layout_->SetSize(GetContentSize());
}

void Window::Pack() {
    if (layout_)
        layout_->Pack();
}

void Window::SetTitle(const std::string& title) {
    titleTextTexture_->SetText(title);
}

void Window::renderLocal() {
    if (showTitleBar_)
        renderTitle();
    renderContext();
}

void Window::renderTitle() {
    Rect titleBarRect = rect(GetPosition().x,
                             GetPosition().y,
                             GetSize().w,
                             GetSize().h - GetContentSize().h);

    GuiContext.renderer->SetDrawColor(GetBgColor());
    GuiContext.renderer->RenderFillRect(titleBarRect);

    titleTextTexture_->TryGenTexture();
    auto& texture = titleTextTexture_->GetTexture();

    GuiContext.renderer->RenderCopy(&texture,
                                    point(10, 0) + titleBarRect.position,
                                    size(0, 0), Color::Black);

    GuiContext.renderer->SetDrawColor(GetBorderColor());
    GuiContext.renderer->RenderRect(titleBarRect);
}

void Window::renderContext() {
    auto contextRect = GetContentRect();

    GuiContext.renderer->SetDrawColor(GetBgColor());
    GuiContext.renderer->RenderFillRect(contextRect);

    if (layout_) {
        if (shouldShowSlidebar()) {
            scrollbar_->SetMin(0);
            scrollbar_->SetMax(layout_->GetSize().h - GetContentSize().h);
            scrollbar_->SetButtonLenPixel((scrollbar_->GetMax() -
                                           scrollbar_->GetMin()) *
                                          (GetContentSize().h /
                                           float(layout_->GetSize().h)));
            auto layoutPos = layout_->GetPosition();
            auto newPos = point(GetContentRect().position.x,
                                GetContentRect().position.y -
                                scrollbar_->GetValue());
            if (layoutPos != newPos) {
                layout_->SetPosition(newPos);
            }
        }
        Rect clipRect = GuiContext.renderer->GetClipRect();
        GuiContext.renderer->SetClipRect(IntersectRects(contextRect,
                                                        clipRect));
        layout_->Render();
        GuiContext.renderer->SetClipRect(clipRect);
    }
    if (shouldShowSlidebar()) {
        scrollbar_->Render();
    }

    GuiContext.renderer->SetDrawColor(GetBorderColor());
    GuiContext.renderer->RenderRect(contextRect);
}

bool Window::shouldShowSlidebar() const {
    return layout_ && layout_->GetSize().h > GetContentSize().h;
}

void Window::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (shouldShowSlidebar()) {
        if (type == MOUSEWHEEL) {
            scrollbar_->SetValue(scrollbar_->GetValue() + GuiContext.wheel * 15);
        }
        if (PointInRect(event::MousePosition(), scrollbar_->GetRect())) {
            SetFocuse(scrollbar_.get());
        }
        if (GetFocuse() == scrollbar_.get())
            scrollbar_->EventHandle(type);
    }
    if (layout_)
        layout_->EventHandle(type);
}


/***********************
 * FoldWindow
***********************/

FoldWindow::FoldWindow(int id,
                       const Point& pos,
                       const Size& size,
                       const std::string& title)
    : Window(id, pos, size, title) {}

Rect FoldWindow::GetRect() const {
    if (IsFold()) {
        return getTitleBarRect();
    } else {
        return Window::GetRect();
    }
}

void FoldWindow::EventHandle(Event type) {
    if (type == Event::MOUSEDOWN) {
        auto titleBarRect = getTitleBarRect();
        auto point = event::MousePosition();
        if (PointInRect(point, titleBarRect)) {
            ToggleFold();
        }
    }
    if (!IsFold()) {
        Window::EventHandle(type);
    }
}

void FoldWindow::renderLocal() {
    if (!IsFold()) {
        Window::renderLocal();
    } else {
        renderTitle();
    }
}

Rect FoldWindow::getTitleBarRect() const {
    Rect titleRect = rect(GetPosition().x, GetPosition().y, 0, 0);
    titleRect.size.h = GuiContext.font->GetUTF8Size(GetTitle()).h;
    titleRect.size.w = GetSize().w;
    return titleRect;
}

/***********************
 * Button
***********************/

Button::Button(int id, const std::string& text, Font* font): Widget(id), font_(font) {
    if (!font_)
        font_ = GuiContext.font;
    textTexture_.reset(new TextTexture(*font_));
    SetText(text);
}

void Button::SetText(const std::string& text) {
    textTexture_->SetText(text);
    SetSize(font_->GetUTF8Size(text));
}

void Button::renderLocal() {
    auto& pos = GetPosition();
    textTexture_->TryGenTexture();
    if (PointInRect(event::MousePosition(), GetRect())) {
        GuiContext.renderer->SetDrawColor(hoverBgColor_);
    } else {
        GuiContext.renderer->SetDrawColor(bgColor_);
    }
    Rect rect(pos.x, pos.y, GetSize().w, GetSize().h);
    GuiContext.renderer->RenderFillRect(rect);

    Size size = textTexture_->GetSize();
    GuiContext.renderer->RenderCopy(&textTexture_->GetTexture(),
                                    rect.position + (rect.size - size) / 2,
                                    size,
                                    textColor_);

    GuiContext.renderer->SetDrawColor(borderColor_);
    GuiContext.renderer->RenderRect(rect);
}

void Button::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (type == MOUSEDOWN) {
        if (eventListener_.clickCb_) {
            eventListener_.clickCb_(this);
        }
    }
}


/***********************
 * ImageButton
***********************/

ImageButton::ImageButton(int id, Texture* texture)
    : Widget(id) {
    SetTexture(texture);
}

void ImageButton::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (type == MOUSEDOWN) {
        if (eventListener_.clickCb_) {
            eventListener_.clickCb_(this);
        }
    }
}

void ImageButton::SetTexture(Texture* texture) {
    texture_ = texture;
    if (texture)
        SetSize(texture->GetSize());
    else
        SetSize(size(0, 0));
}

void ImageButton::renderLocal() {
    if (PointInRect(event::MousePosition(), GetRect())) {
        GuiContext.renderer->RenderCopy(texture_, GetPosition(),
                                        size(0, 0), Color::Gray);
    } else {
        GuiContext.renderer->RenderCopy(texture_, GetPosition(),
                                        size(0, 0), Color::White);
    }
}

/***********************
 * Checkbox
***********************/

Checkbox::Checkbox(int id,
                   const std::string& title,
                   Font* font): Button(id, title, font) {
    boxSize_ = font_->GetFontHeight();
    SetSize(size(boxSize_, 0) + GetSize());
}

void Checkbox::renderLocal() {
    renderBox(GetPosition());
    renderText(GetPosition()+ point(boxSize_, 0));
}

void Checkbox::renderBox(const Point& pos) {
    GuiContext.renderer->SetDrawColor(boxColor_);
    Rect boxRect(pos.x, pos.y, boxSize_, boxSize_);
    if (PointInRect(event::MousePosition(), GetRect())) {
        GuiContext.renderer->SetDrawColor(hoverBoxBgColor_);
    } else {
        GuiContext.renderer->SetDrawColor(boxBgColor_);
    }
    GuiContext.renderer->RenderFillRect(boxRect);

    GuiContext.renderer->SetDrawColor(boxColor_);
    GuiContext.renderer->RenderRect(boxRect);

    if (IsChecked()) {
        Rect innerRect;
        innerRect.size.w = std::ceil(boxSize_ * 0.6);
        innerRect.size.h = std::ceil(boxSize_ * 0.6);
        innerRect.position.x = boxRect.position.x + (boxSize_ - innerRect.size.w) / 2;
        innerRect.position.y = boxRect.position.y + (boxSize_ - innerRect.size.h) / 2;
        GuiContext.renderer->RenderFillRect(innerRect);
    }
}

void Checkbox::renderText(const Point& pos) {
    textTexture_->TryGenTexture();
    Rect rect;
    rect.position = pos;
    rect.size = GetSize() - size(boxSize_, 0);
    GuiContext.renderer->RenderCopy(&textTexture_->GetTexture(),
                                    rect.position, rect.size,
                                    GetTextColor());
}

void Checkbox::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (type == MOUSEDOWN) {
        ToggloeCheckState();
        if (eventListener_.clickCb_) {
            eventListener_.clickCb_(this);
        }
    }
}


/***********************
 * Label
***********************/

Label::Label(int id, const std::string& text, Font* font)
    : Widget(id), font_(font) {
    if (!font_)
        font_ = GuiContext.font;
    texture_.reset(new TextTexture(*font_));
    SetText(text);
}

void Label::SetText(const std::string& text) {
    texture_->SetText(text);
    Size size = font_->GetUTF8Size(text);
    SetSize(size);
}

void Label::renderLocal() {
    texture_->TryGenTexture();

    GuiContext.renderer->RenderCopy(&texture_->GetTexture(),
                                    GetPosition(), GetSize(),
                                    textColor_);
}

void Label::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (type == Event::MOUSEDOWN) {
        if (eventListener_.clickCb_)
            eventListener_.clickCb_(this);
    }
}


/***********************
* Slidebar 
***********************/

Slidebar::Slidebar(int id,
                     int min, int max, int value,
                     int lenPixel,
                     bool vertical)
    : Widget(id),
      min_(min), max_(max), value_(value),
      lenPixel_(lenPixel),
      vertical_(vertical) {
    if (vertical_) {
        SetSize(size(width_, lenPixel));
    } else {
        SetSize(size(lenPixel, width_));
    }
}

void Slidebar::EventHandle(Event type) {
    Widget::EventHandle(type);

    if (type == MOUSEDOWN || type == DRAG) {
        if (vertical_) {
            SetValue((event::MousePosition().y - GetPosition().y) /
                      float(lenPixel_) * (max_ - min_));
        } else {
            SetValue((event::MousePosition().x - GetPosition().x) /
                     float(lenPixel_) * (max_ - min_));
        }
    }
    if (type == MOUSEWHEEL) {
        SetValue(GetValue() + GuiContext.wheel * 15);
    }
}

void Slidebar::renderLocal() {
    Rect rect(GetPosition().x, GetPosition().y, 0, 0);
    Rect buttonRect = getButtonRect();
    if (vertical_) {
        rect.size.w = 10;
        rect.size.h = lenPixel_;
    } else {
        rect.size.h = 10;
        rect.size.w = lenPixel_;
    }

    GuiContext.renderer->SetDrawColor(bgColor_);
    GuiContext.renderer->RenderFillRect(rect);
    if (PointInRect(event::MousePosition(), getButtonRect())) {
        GuiContext.renderer->SetDrawColor(buttonHoverColor_);
    } else {
        GuiContext.renderer->SetDrawColor(buttonBgColor_);
    }
    GuiContext.renderer->RenderFillRect(buttonRect);
    GuiContext.renderer->SetDrawColor(borderColor_);
    GuiContext.renderer->RenderRect(buttonRect);
    GuiContext.renderer->RenderRect(rect);
}

Rect Slidebar::getButtonRect() const {
    Rect buttonRect = {GetPosition().x, GetPosition().y, 0, 0};
    int px = (lenPixel_ - buttonLenPixel_) * float(GetValue() - min_) /
             (max_ - min_);
    if (vertical_) {
        buttonRect.position.y += px;
        buttonRect.size.w = width_;
        buttonRect.size.h = buttonLenPixel_;
    } else {
        buttonRect.position.x += px;
        buttonRect.size.w = buttonLenPixel_;
        buttonRect.size.h = width_;
    }
    return buttonRect;
}


/***********************
 * InputBox
***********************/

InputBox::InputBox(int id, int lenPixel) :Widget(id) {
    SetSize(size(lenPixel, GuiContext.font->GetFontHeight()));
}

void InputBox::EventHandle(Event type) {
    if (type == GET_FOCUSE) {
        SDL_StartTextInput();
        SDL_Rect rect = Rect2SDL(GetRect());
        SDL_SetTextInputRect(&rect);
    }
    if (type == LOST_FOCUSE) {
        SDL_StopTextInput();
    }
    if (type == TEXT_INPUT) {
        char* inputText = event::EventContext.event.text.text;
        int w = GuiContext.font->GetUTF8Size(inputText).w;
        lineX_ += w;
        utf8string str(inputText);
        text_.insert(text_.begin() + cursor_, str);
        cursor_ += str.size();
        resetTextTexture();
    }
    if (type == KEYDOWN) {
        const auto& key = event::EventContext.event.key.keysym.sym;
        if (key == SDLK_BACKSPACE &&
            !event::EventContext.isEditing) {
            if (cursor_ != 0) {
                lineX_ -= getUTF8FontWidth(GuiContext.font,
                                           text_[-- cursor_]);
                text_.erase(text_.begin() + cursor_);
            }
            resetTextTexture();
        }
        if (!event::EventContext.isEditing) {
            if (key == SDLK_LEFT) {
                if (cursor_ > 0) {
                    lineX_ -= getUTF8FontWidth(GuiContext.font,
                                               text_[-- cursor_]);
                }
            }
            if (key == SDLK_RIGHT) {
                if (cursor_ < int(text_.size())) {
                    lineX_ += getUTF8FontWidth(GuiContext.font,
                                               text_[cursor_ ++]);
                }
            }
        }
    }
    Widget::EventHandle(type);
}

int InputBox::getUTF8FontWidth(Font* font, const utf8string::OneUTF8& c) {
    auto str = UTF8ToString(c);
    int w = font->GetUTF8Size(str).w;
    return w;
}

void InputBox::resetTextTexture() {
    textTexture_.reset();
}

void InputBox::renderLocal() {
    Rect rect = GetRect();
    GuiContext.renderer->SetDrawColor(bgColor_);
    GuiContext.renderer->RenderFillRect(rect);

    GuiContext.renderer->SetDrawColor(borderColor_);
    GuiContext.renderer->RenderRect(rect);

    tryGenTexture();
    if (textTexture_) {
        Rect textRect;
        textRect.position = GetPosition();
        textRect.size = textTexture_->GetSize();

        GuiContext.renderer->RenderCopy(textTexture_.get(),
                                        textRect.position, textRect.size);
    }

    if (GetFocuse() == this) {
        GuiContext.renderer->RenderLine(rect.position + point(lineX_, 0),
                                        rect.position + point(lineX_, rect.size.h));
    }
}

void InputBox::tryGenTexture() {
    if (!textTexture_ && !text_.empty()) {
        auto utf8str = text_.to_string();
        auto surface = TTF_RenderUTF8_Blended(GuiContext.font->Raw(),
                                              utf8str.c_str(),
                                              Color2SDL(textColor_));
        textTexture_.reset(new Texture(surface));
        SDL_FreeSurface(surface);
    }
}


/***********************
 * TreeList
***********************/

TreeList::Node::Node(Font* font,
                     TreeList& treelist,
                     const std::string& text,
                     Texture* texture)
    : texture_(*font), treelist_(treelist), image_(texture) {
    texture_.SetText(text);
    texture_.TryGenTexture();
}


TreeList::TreeList(int id,
                   const std::string& rootText,
                   Texture* rootTexture,
                   Font* font)
    : Widget(id), font_(font) {
    if (!font_)
        font_ = GuiContext.font;
    root_.reset(new Node(font_, *this, rootText, rootTexture));
    Pack();
}

void TreeList::EventHandle(Event type) {
    Widget::EventHandle(type);
    eventHandleIter(type, *root_);

}

void TreeList::Remove(Node&) {
    // TODO not implement
    // remenber to delete node, and judge `selectNode_`.
    assert(false);
}

void TreeList::eventHandleIter(Event type, Node& node) {
    int h = calcNodeHeight(node);
    if (type == MOUSEDOWN) {
        if (PointInRect(event::MousePosition(),
                        getFoldSignRect(node))) {
            if (!node.children.empty()) {
                node.expand = !node.expand;
                Pack();
                if (foldToggleCb_) {
                    foldToggleCb_(node);
                }
            }
        } else if (event::MousePosition().y >= node.relatePos_.y + GetPosition().y &&
                   event::MousePosition().y <= node.relatePos_.y + GetPosition().y + h) {
            selectNode_ = &node;
            if (clickNodeCb_) {
                clickNodeCb_(node);
            }
        } else if (!node.children.empty() && node.expand) {
            for (auto& child : node.children) {
                eventHandleIter(type, child);
            }
        }
    }
}

Rect TreeList::getFoldSignRect(const Node& node) {
    Rect result = rect(GetPosition() + node.relatePos_,
                       GetPosition() + node.relatePos_ + size(FoldSignLen, FoldSignLen));
    if (node.image_) {
        result.position.y += (std::max(std::max(result.size.h, node.texture_.GetSize().h), node.image_->GetSize().h) - result.size.h) / 2;
    } else {
        result.position.y += (std::max(result.size.h, node.texture_.GetSize().h) - result.size.h) / 2;
    }
    return result;
}

void TreeList::renderLocal() {
    renderNode(*root_);
}

void TreeList::renderNode(Node& node) {
    auto pos = node.relatePos_ + GetPosition();
    renderHighlightArea(node);
    if (!node.children.empty()) {
        renderFoldSign(node);
    }

    pos.x += FoldSignLen;
    if (node.image_) {
        int y = (std::max(node.image_->GetSize().h, node.texture_.GetSize().h) - node.image_->GetSize().h) / 2;
        GuiContext.renderer->RenderCopy(node.image_, pos + point(0, y));
        pos.x += node.image_->GetSize().w;
    }

    int y = 0;
    if (node.image_) {
        y = (std::max(node.image_->GetSize().h, node.texture_.GetSize().h) - node.texture_.GetSize().h) / 2;
    }

    renderText(node.texture_, pos + point(0, y));
    if (!node.children.empty() && node.expand) {
        for (size_t i = 0; i < node.children.size(); i++) {
            renderNode(node.children[i]);
        }
    }
}

void TreeList::renderHighlightArea(Node& node) {
    if (selectNode_ && selectNode_ == &node) {
        GuiContext.renderer->SetDrawColor(Color::Gray);
        GuiContext.renderer->RenderFillRect(rect(GetPosition().x, node.relatePos_.y + GetPosition().y,
                                                 GetSize().w, calcNodeHeight(node)));
    }
}

void TreeList::renderFoldSign(Node& node) {
    auto rect = getFoldSignRect(node);
    auto renderer = GuiContext.renderer;
    renderer->SetDrawColor(Color::Black);
    renderer->RenderRect(rect);
    renderer->RenderLine(rect.position + point(0, FoldSignLen / 2 - 1),
                         rect.position + point(FoldSignLen - 2, FoldSignLen / 2 - 1));
    if (!node.expand) {
        renderer->RenderLine(rect.position + point(FoldSignLen / 2, 0),
                             rect.position + point(FoldSignLen / 2, FoldSignLen - 2));
    }
}

void TreeList::renderText(TextTexture& texture, const Point& pos) {
    texture.TryGenTexture();
    GuiContext.renderer->RenderCopy(&texture.GetTexture(), pos,
                                    size(0, 0), Color::Black);
}

void TreeList::Pack() {
    SetSize(size(GetSize().w, 0));
    int y = 0;
    packIter(*root_, y, 0);
}

void TreeList::packIter(Node& node, int& y, int indentNum) {
    node.relatePos_.x = indentNum * Indent;
    node.relatePos_.y = y;
    int maxHeight = calcNodeHeight(node);
    auto& s = GetSize();
    int w = std::max(s.w, node.relatePos_.x + node.texture_.GetSize().w + FoldSignLen);
    if (node.image_) {
        w += node.image_->GetSize().w;
    }
    SetSize(size(w, s.h + maxHeight));


    y += maxHeight;
    if (!node.children.empty() && node.expand) {
        for (size_t i = 0; i < node.children.size(); i++) {
            packIter(node.children[i], y, indentNum + 1);
        }
    }
}

int TreeList::calcNodeHeight(Node& node) {
    int h = std::max(node.texture_.GetSize().h, FoldSignLen);
    if (node.image_) {
        h = std::max(h, node.image_->GetSize().h);
    }
    return h;
}

/************************************
 * factory function and widget trace
*************************************/

struct GuiRuntimeContext {
};

Window* CreateWindow(int id,
                     const Point& pos,
                     const Size& size,
                     const std::string& title,
                     bool showTitleBar) {
    Window* window = new Window(id, pos, size, title, showTitleBar);
    GuiContext.stock.Add(window);
    return window;
}

FoldWindow* CreateFoldWindow(int id,
                             const Point& pos,
                             const Size& size,
                             const std::string& title) {
    FoldWindow* window = new FoldWindow(id, pos, size, title);
    GuiContext.stock.Add(window);
    return window;
}

LinearLayout* CreateLinearLayout(int id, bool vertical) {
    auto layout = new LinearLayout(id, vertical);
    GuiContext.stock.Add(layout);
    return layout;
}

GridLayout* CreateGridLayout(int id, const Size& gridSize) {
    auto layout = new GridLayout(id, gridSize);
    GuiContext.stock.Add(layout);
    return layout;
}

AbsoluteLayout* CreateAbsoluteLayout(int id) {
    auto layout = new AbsoluteLayout(id);
    GuiContext.stock.Add(layout);
    return layout;
}

Button* CreateButton(int id,
                     const std::string& text,
                     Font* font) {
    auto button = new Button(id, text, font);
    GuiContext.stock.Add(button);
    return button;
}

Label* CreateLabel(int id,
                   const std::string& text,
                   Font* font) {
    auto label = new Label(id, text, font);
    GuiContext.stock.Add(label);
    return label;
}

Checkbox* CreateCheckbox(int id,
                         const std::string& text,
                         Font* font) {
    auto checkbox = new Checkbox(id, text, font);
    GuiContext.stock.Add(checkbox);
    return checkbox;
}

Slidebar* CreateSlidebar(int id,
                           int min, int max, int value,
                           int lenPixel,
                           bool vertical) {
    auto scrollbar = new Slidebar(id,
                                   min, max, value,
                                   lenPixel,
                                   vertical);
    GuiContext.stock.Add(scrollbar);
    return scrollbar;
}

InputBox* CreateInputBox(int id, int lenPixel) {
    auto inputbox = new InputBox(id, lenPixel);
    GuiContext.stock.Add(inputbox);
    return inputbox;
}

TreeList* CreateTreeList(int id,
                         const std::string& rootText,
                         Texture* rootTexture,
                         Font* font) {
    auto treelist = new TreeList(id, rootText, rootTexture, font);
    GuiContext.stock.Add(treelist);
    return treelist;
}

ImageButton* CreateImageButton(int id, Texture* texture) {
    auto imageButton = new ImageButton(id, texture);
    GuiContext.stock.Add(imageButton);
    return imageButton;
}

void FreeAllWidgets() {
    GuiContext.stock.Clear();
}

Widget* GetFocuse() {
    return GuiContext.focuse;
}

void SetFocuse(Widget* widget) {
    GuiContext.focuse = widget;
}

Widget* FindWidget(int id) {
    auto& cont = GuiContext.stock.GetContainer();
    auto it = std::find_if(cont.begin(), cont.end(),
                        [&](Widget* w){ return w->GetID() == id; });
    if (it == cont.end())
        return nullptr;
    return *it;
}

void Render() {
    for (auto& widget : GuiContext.stock.GetContainer()) {
        if (!widget->GetParent()) {
            widget->Render();
        }
    }
}

void __PointOutFocuseWidget() {
    auto focuse = GetFocuse();
    if (focuse) {
        Rect r = rect(focuse->GetPosition().x, focuse->GetPosition().y,
                      5, 5);
        GuiContext.renderer->SetDrawColor(color(255, 255, 0, 255));
        GuiContext.renderer->RenderFillRect(r);
    }
}

}
